Hey there.
Welcome back.
We're going to continue where we left off with our zombie eye by adding more features like playing different
sound effects for zombie, having our zombie wander around the map, and implementing health regeneration.
Back inside of our zombie AI script.
What we want to be able to do is we want to create a function to allow us to have our zombie wander
around the map when we were not able to generate a path, or we don't have any players nearby to Pathfind
towards.
In these two scenarios, we need to make sure we set the walk speed of our humanoid back to the property's
wandering speed, and then we can have a function, something like wander, call it, and have our zombie
wander around the map.
So we can go up and create a new function.
I will call this function wander.
And inside of here, what we need to do is we actually want to keep track of a boolean to tell us whether
or not the zombie is currently wandering.
And that's because if the zombie is in the wandering state, but then they get shot.
We want to set wandering back to false and that will tell the wandering function hey, we've been shot.
We need to get out and start pathfinding and find the nearest player to us so we can go ahead and create
another boolean.
I'm going to call this wandering and I'm going to set it to false by default.
However, when we start to wander, we could go ahead and set wandering equal to true.
So inside of the wander function we'll set wandering equal to true.
And then we'll basically just generate a random position for our eye to wander towards.
So let's go ahead and also get a random data type.
We'll just call it RNG.
And that's equal to random dot new.
And then we can go down.
And we can generate a random x and z position in the world.
So I'll call this random x and that will be equal to RNG next integer.
And we'll do anywhere between -12 to 12.
And then we can copy this to the exact same thing for the z axis.
And then we can go ahead and just make a new goal position.
I'll just call it goal and it's going to be equal to my root part.
Dot position.
And then we're going to add a new vector three with this random x zero on the y.
And then this random z.
Then we can basically do the exact same thing that we've done in here.
We'll compute a new path.
This time the goal is going to be our goal.
And then we'll compute this new path.
And then if we were not successful in generating this path, then we're basically just going to return
out of this function.
So path dot status is not equal to the enum dot path status of success.
Then we'll just return out of this function.
Check to see if there is any players nearby.
And if there isn't, then we'll go back into the watering function.
It'll generate a new position and we'll try to pathfind to that new position.
Otherwise, if we were successful in generating the path, then we can of course loop through every
single waypoint inside of our path.
Get waypoints.
And we'll have our humanoid move to that waypoint position.
And then we can check if the waypoint action is equal to the enum dot pathway point action of jump,
and if it is, we'll have our humanoid jump.
Otherwise.
Of course, we'll also wait for the move to finish event, but we're going to time out it as well.
So timed out is going to be equal to wait.
With time out we'll pass my humanoid.
Dot moved to finish and then pass the number three.
And if we timed out during this wandering phase, then we could also print in here something like zombie
timed out while wandering.
Is it stuck?
And then we'll just force our humanoid to jump again.
And then we'll just break out of this loop and try to generate a new point to Pathfind towards.
Otherwise, this for loop is another good opportunity to see if, um, our wandering variable is not
true.
So if not wandering, then this would mean that we got shot by a player, because what we can go ahead
and do is when we're listening for when the health of our zombie changes, when we set the targeting
distance to the alert targeting distance, we can also go ahead and set wandering equal to false.
And that way if we are wandering, it will notify when we are in this for loop that hey!
We got shot, so we should go ahead and break out of this loop and check to see if there are any nearby
players that we can pathfind towards.
Otherwise, what we can go ahead and do is after we are done wandering, I want to have our zombie kind
of stand still for a couple seconds before our zombie goes and starts wandering to a new waypoint.
So after this for loop is over, if our wandering variable is still true, then we can go ahead and
just yield randomly.
We could do RNG next number and wait anywhere between 1 and 3 seconds.
And then once that time is up then we'll check to see if there's any nearby players.
And if there isn't, we'll go back into our wandering phase and start wandering around the map again.
Now, this is also a good opportunity for us to implement all of the different sound IDs inside of our
properties, such as attacking the death sound when we see a player, or just random growling sounds.
So we can go ahead and create a function to basically pick an ID randomly out of these arrays we're
going to be using, and then play that sound on the head of our zombie.
So we'll call this function play random sound.
We'll get passed an ID list.
And then the parent we would like to set the sound to.
And then we'll make a new sound instance.
We'll make sure we'll set the sound ID equal to.
And inside of this ID list we're going to randomly grab an ID out of there.
So one to the max number of elements in the table.
And then we'll make sure to loop through every single property and value inside of our properties.
Sound properties and apply that on our sound.
And then we can go ahead and set the parent of our sound equal to the parent pass to the function.
We'll play that sound.
And then when that sound ends, we'll make sure to destroy it and clean it up.
So now we can go ahead and just start playing random sounds.
For example, when we are wandering around the map.
So through each iteration of our for loop we can have an if statement check to see.
For example, let's say we have a 10% chance through each iteration of this loop to play a random growling
sound.
So what we can do is we can generate a random number.
So rng next integer 1 to 10.
And if this value is equal to one then we'll play a random growling sound.
So we could do properties dot growl sound ids.
And the parent is going to be the head of our zombie.
And then we can just copy this and kind of do this in several other areas.
For example, when we were computing a path and we're chasing after a player.
If we are, let's say, repeatedly running towards a player, we could also randomly play a growling
sound so we could just, you know, paste that same kind of if statement in here and we'll just play
a random growling sound.
However, we do need to note that this repeat loop is executing much faster than that for loop.
So let's go ahead and decrease the odds a bit.
And we could set 10 to 150 instead.
So we have a smaller chance to play a random growling sound.
And then we can do the exact same thing.
For example, in our get nearest player function, we had those player scene sound IDs.
So what we can do is if we got a closest target, then we'll just paste this in here and randomly play
one of the player scene sound IDs.
And then we can use the same function.
When our zombie dies, we can play one of the death sounds.
So when our zombie dies, we'll call play random sound and we'll pass properties dot death sound IDs,
and we'll play that on the head of the zombie.
And then another thing we want to make sure we do is that when we are wandering after we break out of
this wandering function, we want to make sure we go ahead and set wandering back to false because we
are no longer wandering.
And I'll make sure to copy this and put this in both of these sections.
And another important thing we want to implement is the ability for our zombie to attack a player.
So inside of our compute path to function, when we see a humanoid and we're running directly after
them, we should be able to also like call a function here.
We could call it like attack and basically attack a player, check to see if they're close enough,
and if the zombie is close enough and within range to attack the player, then we'll deal damage to
them so we can have a function where we pass the target to it.
And then I'll go up and make a new attack function.
And then we basically want to make sure we have a cooldown to determine when we are able to attack.
Because if you remember in our properties table, we set a cooldown between attack property.
So what I'm going to do is I'm going to create another boolean and I'm going to call this one Can Attack.
I'm going to set this to true by default.
And then we could go back down to our attack function.
And inside of our attack function, we want to check whether or not we can actually attack.
So if we cannot attack, then we're just going to return out of this function.
Another thing we need to verify is if we are within range to attack our player.
So if my root part dot position subtracted by the target's position.
And then get the magnitude of that and see if that is greater than our properties.
Dot.
Attack range, then clearly we are not within range to attack this player, so we're just going to return.
Otherwise we're going to set can attack equal to false.
And then we're going to refer to our target parent, get the humanoid of that player, and then use
the take damage function to deal some damage.
And that's going to be equal to our properties.
Dot damage per hit.
And then since we don't want this function to yield, we're just going to delay a function in a new
thread instead.
And we're going to delay it for the properties dot cooldown between attack.
And then inside of this function, we can just simply set can attack back to true.
We could also do the same thing of playing a random sound.
So inside of here, let's say we had a one out of four chance or 25% chance to play one of the attack
sound IDs, so we'll do something like that.
So now our zombie should have the ability to wander around the map as well as attack our player and
play random sounds while he is wandering, or while he's attacking, or when he dies.
So what we can go ahead and do is we can run our game instead to go ahead and see our zombie in action.
And as you can see, it says our zombie is wandering.
And he paused in place and now he's wandering to a new position.
And if you heard, he randomly played a sound.
There you go.
He played another one.
Now our zombie seems a lot more lively since he's playing these different sounds and he's just kind
of slowly wandering around our map, which is kind of cool.
Now let me go ahead and move my zombie all the way.
Um, let's say we put them all the way inside of here.
And what we're going to go ahead and do.
Is we're going to spawn the player into the map with a weapon, and we're going to see if we can shoot
our zombie and see if that increases the targeting distance for the zombie.
So I'm going to go into server storage.
I'm just going to copy one of these guns, like the assault rifle, put it in my starter pack, and
then I'm going to playtest my game.
And hopefully my zombie stays wandering.
So now that I'm on the map, great, it still says my zombie is wandering around.
Let me see if I can actually highlight him in the workspace.
So my zombie is over there wandering around.
Very cool.
Let me see if I can get a vantage point to kind of shoot him from far away.
So if I stand up here, let me see if I'm able to shoot him.
If I can get a good vantage point.
There we go.
I hit him a couple times.
And I don't know if it increased his wandering distance.
Oh, it definitely did because he is now coming after us.
So after we shot him, he definitely increased the range of his wandering distance.
And now he is coming to get us and he can see us and he's starting to run towards us.
Now you can see that there is a bit of a problem here, and that our zombie is not jumping when he encounters
these obstacles.
So that means we need to basically listen, um, one fix we could do for it is we could listen for when
his lower torso touches something, and when his lower torso touches something, we can force him to
jump, and that will allow him to jump over obstacles.
But as it stands at the moment, he is not able to jump over these obstacles.
But let's go ahead and see if he is able to hurt us.
So if I stand next to him, as you can see, he just herded me.
And he's hurting me again.
So as you can see, our zombie is very nice, very cool.
And you can hear him playing the attack sounds as well.
Let me see if he plays another one.
Come on, buddy, play it for me.
Ah.
Yeah, there we go.
He played one of the attack.
Sounds very cool.
And then if we kill our zombie, he should play the death sound and then get cleaned up off the map.
So let's go ahead and kill him.
There we go.
He played his death sound, and then he should get cleaned up.
There you go.
He cleaned himself off the map.
And you could also see that this print statement printed within our server script as well, it says
I am dead or can't see the player or the player is dead.
So while he was chasing after us through the next iteration of that repeat loop, he realized that his
alive variable is set to false.
So he's like, yep, I'm dead.
I'm going to break.
I'm going to stop pathfinding.
And then it goes back to the while loop.
The while loop goes to the next iteration, but it also sees that the alive variable is set to false.
So it just stops executing.
And the main thread of execution reaches the end of this script and there's nothing to execute anymore.
So now what we can go ahead and do is allow our zombie to jump when his lower torso touches something,
as well as implementing the health regeneration for a zombie.
And let's go ahead and do that in a separate script.
So inside of our zombie we can go ahead and create a new script.
And I'm going to call this my property listeners.
Inside of here, I'm going to be using the same common template.
And I'm going to go ahead and make a reference to the properties of my zombie.
So that'll be require script dot parent dot properties.
And I'm also going to get the humanoid for my zombie.
So we'll do script dot parent dot humanoid.
And then we'll get a random data type as well.
And that's because we're also going to listen in here for when our humanoid dies.
And then we're going to award a random amount of money to our players based on that money drop property
we have right here.
So inside of here, inside of the handler section, what I want to check is first I want to check if
inside of our agent params that we have, the agent can jump property set to true.
If it is set to true, then we're okay to listen for when our lower torso is touched to go ahead and
force the zombie to jump.
If we've explicitly set that the zombie cannot jump, then I don't want the zombie to ever jump.
So what I'm going to do is actually I'll make another variable for my zombie, which is script dot parent.
And then that way I can go ahead and replace these references with my zombie.
And then we can go ahead and listen for when the zombie dot lower torso is touched.
And when it is touched, we can go ahead and force the zombie to jump.
Now I only want the zombie to jump if the thing that touched his torso did not belong to a character
model, so he didn't touch another zombie, or he didn't touch a player's character model.
And I also want to make sure the part that he touched didn't have can collide enabled.
So, for example, when our zombie is walking through invisible walls like this one, the can touch
event is still going to fire, which means he's going to jump even though this is a non collidable transparent
part.
So what we can go ahead and do.
Is, we can check if the part that touched the lower torso of our zombie.
If the parent find first child, which is a humanoid.
So if it belongs to a character model or let's say that can collide is disabled for this part.
So if it's not collidable, then we're just going to return and not force our zombie to jump.
The next thing we want to listen for is if we have the regenerates health property set to true.
If we do, then we want to listen for when the health changed event on our humanoid fires, we'll connect
a function.
And what we want to go ahead and do is we want to create a debounce.
So we want to store in a variable if we are already healing our zombie.
So we'll set healing equal to false.
And then inside of here, if we're already healing we'll return.
Otherwise we're going to set healing equal to true.
And actually, before we set healing equal to true, we also want to make sure if our humanoid dot health.
Is less than or equal to my humanoid max health, and we multiply that by the properties dot and I believe
it was called what was it called?
Health regen limit.
So we'll do the health regen limit.
So if our health is greater than or equal to the health region limit, then that means we can't regenerate
any health and we're just going to return.
Otherwise, if our health is below that limit and then we can have a repeat loop repeatedly increase
the health of our humanoid until our humanoid reaches that health limit, or our humanoid is dead.
So we'll do my humanoid dot health plus equal the properties dot health regen amount.
And then we're going to yield for our properties dot health region tick.
And then we're going to keep healing our humanoid until my humanoid dot health is greater than or equal
to my humanoid dot max health multiplied by the properties dot healing or health region limit.
Or if my humanoid dot health is less than or equal to zero.
And then once we're out of that repeat loop, we can go ahead and set healing equal to false.
Now, the last thing we want to go ahead and listen for is when our humanoid dies.
So my humanoid will listen to the died event and we'll connect a function.
We want to check to see if the cause of death for this humanoid was a player.
So we're going to see if there is anything within the who last damaged object value inside of our zombie.
So I'll create a variable called player, and it's going to be equal to my zombie Dot who last damaged
and get the value inside of there.
If there is no value inside of that object value, then we're just going to return.
And then I'm just going to double check to make sure that the value stored in our object value actually
belongs to a player.
So if this player is not actually a player instance for some reason, then we're also going to return.
Otherwise what we can go ahead and do is we can increment that kills value within the player's leader
stats.
So kills dot value plus equal one.
And then we can also give our player some money.
So player dot leader stats dot money dot value.
And we're going to increment it by and we're going to use RNG next integer.
And pick a random value between the properties dot money drop dot minimum and properties dot money drop
dot maximum.
So now our zombies should be able to jump when we've defined in the properties that it can jump, our
zombies should be able to regenerate health.
When we've defined that, it can regenerate health, and our zombie should accredit a kill and money
to a player that killed it.
So let's go ahead and test this all out.
My zombie should still be up.
He's still back there.
And if I go and play test my game.
Currently my zombie is wandering, which is cool.
I'll spawn into the map.
And then I'll show you that actually his wandering or targeting radius increased because you remember
he was back inside of there.
And when I was standing right here, he was starting to pathfind towards us.
But as you can see, I'm standing in the same area and our zombie is not pathfinding towards us because
his radius has not increased.
But if I were to shoot him, let me go ahead and find my zombie.
He's back there somewhere.
So if I were to pull out my gun and let me see if I can get a shot on him.
Make it a little bit closer.
Hey, still isn't pathfinding towards us, but if I get a shot on him.
Okay, I got a shot on him.
As you can see, his targeting radius increased and now he is coming to Pathfind towards us.
And you can see each path.
And as he is pathfinding.
His pathfinding goes beyond 20 studs, and he goes and generates a new path to follow me to.
As you can see, here he is pathfinding.
And eventually he sees us and he's chasing after us.
Is he?
Is he screaming?
Yep.
There we go.
He's he wants to eat us.
And if I go yep.
If I touch him or get close to him, he starts hurting me.
It continues to hurt me.
And if I get his health below, let's say half health.
So let me get him down low.
Oh, man, I accidentally killed them.
Oops.
Let's go ahead and play test again and try to get him below half health.
Is pathfinding to us.
And then actually let me get a headshot on him.
Okay, he's below half health.
He should start regenerating health.
And as you can see, if you look, his health is slowly starting to increase.
And then when he hits the halfway mark in his health.
Yep.
As you can see, he's no longer regenerating any health anymore.
Very cool.
You can also see that he jumped when he hit that, uh, barrier right there.
So if I walk on the other side of this barrier.
As you can see, it is forcing him to jump because that barrier is touching his lower torso.
But if I stand there, you go.
As you can see, he jumps over the sandbag barrier.
Because it's touching his lower torso.
It's continuing to pathfind towards us.
Then eventually if we go around this vehicle, he's going to generate a new path to come to us.
And he's coming around.
He's coming around generating his path.
And then eventually he sees us again.
And then he touched that barrier and it forced him to jump up.
Touches that.
It forces him to jump.
And he's continuing to come after us.
And if we shoot him and kill him, as you can see, it incremented our kills and it gave us some money
from him.
Very cool.
I'm going to go ahead and comment out the section of code where we're generating parts for the zombies
path.
So let me just comment all of that out, and then let's go ahead and playtest our game one last time
just to make sure our zombie is working okay.
We go to our zombie or run over here.
Let's see.
As you can see, he's running after us.
He's running after us.
He wants to eat us.
If I go hide behind here, he's going to start pathfinding around when actually, as you can see, his
pathfinding is no longer laggy, which is kind of interesting.
I think it's just because I'm recording at the same time.
But as you can see, his pathfinding isn't lagging right now.
And then he sees me and he's coming after me.
He's damaging me.
Then if he touches the sandbag, there you go.
It forces him to jump.
Or if I come up here, as you can see, his pathfinding and jumping towards me.
And even if I were to go and climb up onto this, uh, fountain right here, he's still going to pathfind
towards me and get to me.
So as you can see, he's generating his path and boom, he got to me and he's attacking me.
Very cool.
Then I'm going to run away.
And he sees me and he's coming after me, and I'm going to go around here.
And now he's generating a path to come towards to me.
There you go.
He found me and he's coming after me again.
And he ain't going to stop until I kill him.
Okay.
We should have basically everything done with our zombie AI.
I'm going to take a few minutes to copy the scripts and put them inside of my other zombies, and I'm
going to modify this properties table for each one of the zombies inside of my game.
So let me move all of these guys into the workspace so you can go ahead and take a look at every single
one of them, and I'll move this guy back to where he belongs.
So here's all the zombies that we're going to have in our game.
And I'm going to go ahead and copy this module script and put it inside of each one of my zombies and
give them all their own custom abilities and properties and stuff like that.
And then I'll get back to you when I am finished doing that.
Okay, so I've gone ahead and modified all of the different module scripts for each one of my zombies,
and I also copied and pasted both the zombie I and the properties listeners scripts into each one of
my zombies.
And now that means every single one of these zombies here should be working, and they should be all
their own unique zombies with their own unique speeds, their own unique health and stuff like that.
So if we go and play test the game, all of these zombies should come running after me.
As you can see, there's all of these zombies in our game.
There's that one lightning zombie I made.
He's very fast and he's coming after me.
Let me get rid of him.
But he doesn't have a lot of health.
But as you can see, there's all the other zombies coming after me to attack me.
I got some boss zombies here, including the sky would kill him.
And then I also got this guy chasing after me.
This is going to be like, the ultimate guy we're going to have at the last wave in our game.
He has a lot of health, as you can see, so it's going to take a lot of bullets to kill this guy.
But as you can see, each one of these zombies has their own unique health, their own unique style.
For example, this is a slow zombie, but he does deal a lot more damage.
And then there's some boss variants of each of the zombies.
But as you can see, they're all chasing after me and they're all pathfinding to me.
So as you can see, now that I'm hiding, all of them are generating their paths to come after me.
And then once they see me, they're running after me.
Let me see if I can try to get far enough away from these guys so they no longer chase after me.
Okay.
Yeah.
So that guy back there lost interest in pathfinding after me because I got too far away.
And as you can see.
He's just randomly wandering around the map back there.
He don't care about me anymore because I'm too far away.
But now that I'm.
Yeah, now that I'm in range, she's going to start pathfinding after me again, I believe.
So let's go ahead and see.
Yep.
He's pathfinding to come after me.
And then he sees me and now he's running after me.
So very cool.
If you would like to use these zombies with all of the variants that I've added to them with their configured
properties, then I will have all of these zombies attached to the lecture for you to use in your game.
Other than that, we should basically have everything done with our zombie AI.
The only thing we have left to add are going to be the animations for our zombies, and we'll be doing
that in the next lecture, so I'll see you there.